import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { createHmac } from 'crypto';

@Injectable()
export class CryptoService {
  /**
   * 只读属性，使用的加密算法，默认值为'aes-128-cbc'
   * 加密算法要求key和iv长度都为16
   */
  private readonly algorithm: string = 'md5';

  // 构造函数，接收一个只读的私有变量config，类型为ConfigService
  constructor(private readonly config: ConfigService) {}

  /**
   * 生成指定长度的随机字符串
   * @param length 随机字符串长度
   * @returns
   */
  private randomString(length: number) {
    // 初始化结果字符串
    let result = '';
    // 定义字符集，包括大小写字母和数字
    const characters =
      'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    // 获取字符集长度
    const charactersLength = characters.length;
    // 循环指定次数，生成随机字符串
    for (let i = 0; i < length; i++) {
      // 从字符集中随机选择一个字符，并添加到结果字符串中
      result += characters.charAt(Math.floor(Math.random() * charactersLength));
    }
    // 返回生成的随机字符串
    return result;
  }

  /**
   * 获取加密密钥，计算只读属性
   */
  // private key() {
  //   return this.randomString(12);
  // }

  /** 随机盐值后缀 */
  get salt() {
    return this.randomString(12);
  }

  /**
   * 获取盐值
   * @param salt 盐值，默认为空字符串
   * @returns
   */
  getEntireSalt(salt: string) {
    // 1、获取配置中的加密前缀，如果没有则默认为'jckj_'
    // 2、将盐值拼接到加密前缀后面，此盐值保存在数据库中；
    // 3、返回拼接后的字符串
    const prefix = this.config.get<string>('CRYPTO_PREFIX') || 'jckj_';
    // 将盐值拼接到加密前缀后面
    const entireSalt = prefix + salt;
    return entireSalt;
  }

  /**
   * 加密函数
   * @param password 需要加密的密码
   * @param salt 加密密钥盐值
   * @returns
   */
  encrypt(password: string, salt: string) {
    // 创建一个HMAC对象，使用指定的算法和盐
    const hmac = createHmac(this.algorithm, Buffer.from(salt));
    // 更新HMAC对象的内容，使用密码
    const up = hmac.update(password);
    // 计算HMAC对象的摘要
    const res = up.digest();
    // 将摘要转换为十六进制字符串并返回
    return res.toString('hex');
  }

  /**
   * 验证密码
   * @param password 需要验证的密码
   * @param signature 签名；加密的密码
   * @returns
   */
  verify(password: string, signature: string, salt: string) {
    // 加密密码
    const digest = this.encrypt(password, salt);
    // 返回加密后的密码与签名是否相等
    return digest === signature;
  }
}
